﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Fortuitus.Core
{
    public class Vector : IVector
    {
        protected double[] _numbers;
        private Dictionary<string, object> _metaDataStore;
        private Dictionary<string, MetricCacheValue> _metricStore;

        /// <summary>
        /// The length of the vector.
        /// </summary>
        public int Length
        {
            get
            {
                return _numbers.Length;
            }
        }

        /// <summary>
        /// Indexer property to access a number at a specific index.
        /// </summary>
        /// <param name="indx">The index of the number.</param>
        /// <returns>The number at the given index.</returns>
        public double this[int indx]
        {
            get { return _numbers[indx]; }
        }

        /// <summary>
        /// Invalidates all cached metrics.
        /// </summary>
        protected void InvalidateMetricCache()
        {
            foreach (var item in _metricStore)
            {
                item.Value.IsValid = false;
            }
        }

        /// <summary>
        /// Gets a metric of the this vector.
        /// </summary>
        /// <param name="metricKey">The name of the metric.</param>
        /// <returns>The value of the metric.</returns>
        public double GetMetric(string metricKey)
        {
            if (_metricStore.ContainsKey(metricKey))
            {
                if (_metricStore[metricKey].IsValid == false)
                {
                    _metricStore[metricKey].Value = _metricStore[metricKey].Function(this);
                    _metricStore[metricKey].IsValid = true;
                }
                return _metricStore[metricKey].Value;
            }
            else
                throw new ArgumentException("Unknown metric key.");

        }

        /// <summary>
        /// Sets a metric with a specific function.
        /// </summary>
        /// <param name="metricKey">The access key for the metric.</param>
        /// <param name="metricFunction">The function to calculate the metric.</param>
        /// <param name="lazyCalculation">An value that indicates whether the metric shoulds be calulated immediately or if needed.</param>
        public void SetMetric(string metricKey, Func<IVector, double> metricFunction, bool calclulateOnInset = false)
        {
            if (_metricStore.ContainsKey(metricKey))
                _metricStore[metricKey] = new MetricCacheValue() { Function = metricFunction, IsValid = false };
            else
                _metricStore.Add(metricKey, new MetricCacheValue() { Function = metricFunction, IsValid = false });

            if (calclulateOnInset)
            {
                _metricStore[metricKey].Value = _metricStore[metricKey].Function(this);
                _metricStore[metricKey].IsValid = true;
            }
        }

        /// <summary>
        /// Checks whether a certain metadata key exists.
        /// </summary>
        /// <param name="metaDataKey">The metadata key to check</param>
        /// <returns>A value indicatin whether the metadata exists.</returns>
        public bool HasValue(string metaDataKey)
        {
            bool ret = _metaDataStore.ContainsKey(metaDataKey);
            return ret;
        }

        /// <summary>
        /// Access metadata value with a specific key. 
        /// </summary>
        /// <param name="metaDataKey">The metadata key to access the stored value.</param>
        /// <returns>The metadata value.</returns>
        public object GetValue(string metaDataKey)
        {
            if (_metaDataStore.ContainsKey(metaDataKey))
                return _metaDataStore[metaDataKey];
            else
                throw new ArgumentException("Unknown metadata key.");

        }

        /// <summary>
        /// Sets a metadata value with a specific key.
        /// </summary>
        /// <param name="metaDataKey">The key that's used to store the metadata.</param>
        /// <param name="value">The value to store in the metadata store.</param>
        public void SetValue(string metaDataKey, object value)
        {
            if (_metaDataStore.ContainsKey(metaDataKey))
                _metaDataStore[metaDataKey] = value;
            else
                _metaDataStore.Add(metaDataKey, value);
        }

        /// <summary>
        /// Generic version to access metadata value with a specific key. 
        /// </summary>
        /// <typeparam name="T">The type of the value expected. If the stored value of a different type then an exception will be thrown.</typeparam>
        /// <param name="metaDataKey">The key to access the metadata.</param>
        /// <returns>The stored value.</returns>
        public T GetValue<T>(string metaDataKey)
        {
            object val = GetValue(metaDataKey);
            if (val is T)
                return (T)val;
            else
                throw new ArgumentException("The stored metadata is of different type then expected.");
        }

        /// <summary>
        /// Sets a metadata value with a specific key.
        /// </summary>
        /// <param name="metaDataKey">The key that's used to store the metadata.</param>
        /// <param name="value">The value to store in the metadata store.</param>
        public void SetValue<T>(string metaDataKey, T value)
        {
            this.SetValue(metaDataKey, (object)value);
        }

        /// <summary>
        /// Get's an generic enumerator over the numbers within the vector.
        /// </summary>
        /// <returns>Then enumerator over the numbers.</returns>
        public IEnumerator<double> GetEnumerator()
        {
            for (int i = 0; i < _numbers.Length; i++)
            {
                yield return _numbers[i];
            }
        }

        /// <summary>
        /// Get's an generic enumerator over the numbers within the vector.
        /// </summary>
        /// <returns>Then enumerator over the numbers.</returns>
        IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _numbers.GetEnumerator();
        }


        /// <summary>
        /// The default ctor to construct the vector.
        /// </summary>
        /// <param name="id">The id of the vector.</param>
        /// <param name="numbers">The numbers to populate the vector.</param>
        public Vector(IEnumerable<double> numbers)
        {
            _metaDataStore = new Dictionary<string, object>();
            _metricStore = new Dictionary<string, MetricCacheValue>();
            _numbers = numbers.ToArray();

        }
    }
}
